require 'delegate'

class Hashshashin < SimpleDelegator
  # Adds the ability to cherrypick specific key/value pairs from a hash
  #
  # Example:
  #
  #   x = { a: 1, b: 2, c: 3 }
  #   y = Hashshashin.new(x)
  #
  #   y.pick
  #   => {}
  #
  #   y.pick(:a, :c)
  #   => { a: 1, c: 3 }
  #
  #   y.pick(:a, :c, :d)
  #   => { a: 1, c: 3, d: nil }
  #
  def pick(*keys)
    values = values_at(*keys)
    Hash[keys.zip(values)]
  end

  # See:
  # http://stackoverflow.com/a/12360142/321897
  #
  # By default, remove any key/value pairs recursively from the hash if the value is
  # nil, 0, false, empty or a blank string.
  # Alternatively, a block can be passed in which specifies the condition for whether the
  # key/value pair should be excised.
  #
  # Example:
  #
  # {
  #   hash: {
  #     false: false, nil: nil, zero: 0, empty_string: '', empty_list: [], empty_hash: {}, one: 1
  #   },
  #   false: false,
  #   nil: nil,
  #   zero: 0,
  #   empty_string: '',
  #   empty_list: [],
  #   empty_hash: {},
  #   one: 1
  # }
  #
  # Becomes:
  #
  # { hash: { one: 1 }, one: 1 }
  #
  def recursive_compact!
    self.delete_if do |key, value|
      if block_given?
        yield(key, value)
      else
        value.nil? || value === 0 || value === false || empty?(value)
      end
    end

    self.each do |key, value|
      value = Hashshashin.new(value) if value.is_a?(Hash)

      if value.is_a?(Hashshashin)
        if block_given?
          self[key] = value.recursive_compact!(&Proc.new)
        else
          self[key] = value.recursive_compact!
        end
      end

    end

    return self
  end

  # The same as #recursive_compact! above, except this doesn't modify self; instead,
  # this returns a new hash with the recursive compaction applied.
  #
  def recursive_compact(&block)
    Marshal.load(Marshal.dump(self)).recursive_compact!(&block)
  end

  # By default, call #to_datetime, then #rfc3339 on the values for every key/value pair
  # in the hash where the value is a Time or DateTime object, or responds to
  # #to_datetime.
  # Alternatively, a block can be passed in that specifies the acceptance condition for
  # key/value pairs in the hash.
  #
  # Note: Regardless of the acceptance block, we will call #to_datetime, then #rfc3339
  # on the value.
  #
  # Example:
  #
  # {
  #   hash: {
  #     time: '2012-12-05 12:26:54 -0800', # Time.now (object) shown as a string.
  #     datetime: 'Wed, 05 Dec 2012 12:26:56 -0800' # DateTime.now (object) shown as a string.
  #   },
  #   time: '2012-12-05 12:26:54 -0800', # Time.now (object) shown as a string.
  #   datetime: 'Wed, 05 Dec 2012 12:26:56 -0800' # DateTime.now (object) shown as a string.
  # }
  #
  # Becomes:
  # {
  #   hash: {
  #     time: '2012-01-01T12:26:54-08:00', # String
  #     datetime: '2012-01-01T12:26:56-08:00' # String
  #   },
  #   time: '2012-01-01T12:26:54-08:00', # String
  #   datetime: '2012-01-01T12:26:56-08:00' # String
  # }
  #
  def recursive_rfc3339!
    hash = self.dup

    hash.keep_if do |key, value|
      if block_given?
        yield(key, value)
      else
        time?(value) || datetime?(value) || rfc3339able_and_not_string?(value)
      end
    end

    hash.each do |key, value|
      datetime = value.to_datetime
      hash[key] = datetime.rfc3339 if datetime.respond_to?(:rfc3339)
    end

    self.merge!(hash)

    self.each do |key, value|
      value = Hashshashin.new(value) if value.is_a?(Hash)

      if value.is_a?(Hashshashin)
        if block_given?
          self[key] = value.recursive_rfc3339!(&Proc.new)
        else
          self[key] = value.recursive_rfc3339!
        end
      end

    end

    return self
  end

  # The same as #recursive_rfc3339! above, except this doesn't modify self; instead,
  # this returns a new hash with the recursive time formatting applied.
  #
  def recursive_rfc3339(&block)
    Marshal.load(Marshal.dump(self)).recursive_rfc3339!(&block)
  end

  # Replace all string keys with symbols.
  # ie.
  #
  #   Hashshashin.new({ 'a' => 1, 'b' => 2 }).recursive_symbolize_keys!
  #
  #   => { a: 1, b: 2 }
  #
  def recursive_symbolize_keys!
    string_pairs = self.select { |key, value| key.kind_of?(String) }

    string_pairs.each do |key, value|
      self[key.to_sym] = value
      self.delete(key)
    end

    self.each do |key, value|
      value = Hashshashin.new(value) if value.is_a?(Hash)

      self[key] = value.recursive_symbolize_keys! if value.is_a?(Hashshashin)
    end

    self
  end

  # The same as #recursive_symbolize_keys! above, except this doesn't modify self;
  # instead, this returns a new hash with the recursively defined symbolized keys.
  #
  def recursive_symbolize_keys
    Marshal.load(Marshal.dump(self)).recursive_symbolize_keys!
  end

  # This method takes a block that returns an array of [key, value] pairs.
  # It then returns a new hash where each [key, value] pair in the original hash has been
  # replaced with those returned by the block.
  #
  # For example:
  #
  #   h = { a: 1, b: 2, c: 3, d: 4 }
  #
  # Hashshashin.new(h).recursively_apply do |key, value|
  #   if v < 3
  #     [key, value + 10]
  #   else
  #     [key, value]
  #   end
  # end
  #
  # # => { a: 11, b: 12, c: 3, d: 4 }
  #
  def recursively_apply
    return self unless block_given?

    new_hash = Hash[ self.collect { |key, value| yield(key, value) } ]

    new_hash.each do |key, value|
      value = Hashshashin.new(value) if value.is_a?(Hash)

      new_hash[key] = value.recursively_apply(&Proc.new) if value.is_a?(Hashshashin)
    end

    new_hash
  end

  private

    def time?(value)
      value.kind_of?(Time)
    end

    def datetime?(value)
      value.kind_of?(DateTime)
    end

    def rfc3339able_and_not_string?(value)
      value.respond_to?('to_datetime') && !value.kind_of?(String)
    end

    def empty?(value)
      empty = value.empty? if value.respond_to?('empty?')

      strip_empty = if value.respond_to?('strip') && value.respond_to?('empty')
        value.strip.empty?
      end

      empty || strip_empty
    end
end
